import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.sql.SQLException;
import java.util.Enumeration;
import java.util.Vector;
import java.util.concurrent.ConcurrentHashMap;

class TraitementClient extends Thread
{
	private InputStream in;
	private OutputStream out;
	private DataInputStream entree;
	private DataOutputStream sortie;
	
	private Serveur serveur;
	private Socket socket;
	
	private String nomClient;

	public TraitementClient(Serveur serveur, String nomClient, Socket transfert)
	{
		this.serveur = serveur;
		this.socket = transfert;
		this.nomClient = nomClient;
		recupererFlot(transfert);
	}

	public void run()
	{
		int data = 0;
		boolean continuer = true;
		
		while(continuer)
		{
			try {
				data = this.entree.readInt();
				try {
					switch(data)
					{
					case CodesTchat.codeConnexion:
						gererCodeConnexion();
						break;
					case CodesTchat.codeCreationSalon:
						gererCodeCreationSalon();
						break;
					case CodesTchat.codeRejoindreSalon:
						gererCodeRejoindreSalon();
						break;
					case CodesTchat.codeEnvoiMessage:
						gererCodeEnvoiMessage();
						break;
					case CodesTchat.codeDemandeInvitation:
						gererCodeDemandeInvitation();
						break;
					case CodesTchat.codeEjecterTchatteur:
						gererCodeEjecterTchatteur();
						break;
					case CodesTchat.codeDeconnexion:
						gererCodeDeconnexion();
						continuer = false;
						break;
					case CodesTchat.codeCreerCompte:
						gererCodeCreationCompte();
						break;
					case CodesTchat.codeSupprimerSalon:
						gererCodeSupprimerSalon();
						break;
					default:
						//Erreur : code inconnu
						gererCodeInconnu();
						break;
					}
				}
				catch (Exception e)
				{
					System.out.println("Erreur de protocole :");
					System.out.println(data);
				}
			}
			catch (Exception e)
			{
				System.out.println(e.getMessage());
			}
		}
	}
	
	private void gererCodeInconnu() {
		try {
			this.sortie.writeInt(CodesTchat.codeErreurCommande);
		} catch (IOException e) {
			System.out.println(e.getMessage());
		}
	}

	private void gererCodeEjecterTchatteur() {
		// TODO Auto-generated method stub
		
	}

	private void gererCodeDemandeInvitation() {
		// TODO Auto-generated method stub
		
	}

	private void gererCodeEnvoiMessage() {
		// TODO Auto-generated method stub
		
	}

	private void gererCodeRejoindreSalon() throws SQLException, ClassNotFoundException {
		String nomSalon = null;
		try {
			nomSalon = this.entree.readUTF();
		} catch (IOException e) {
			System.out.println(e.getMessage());
		}
		
		String password = null;
		try {
			password = this.entree.readUTF();
		} catch (IOException e) {
			System.out.println(e.getMessage());
		}
		
		int nbPlaces = Connexion_BD.getNbPlaces(nomSalon);
		if(serveur.nbClients(nomSalon) >= nbPlaces || Connexion_BD.estBanni(nomClient, nomSalon))
		{
			//Si le salon est indisponible ou le client est banni
			try {
				this.sortie.writeInt(CodesTchat.codeSalonIndisponible);
			} catch (IOException e) {
				System.out.println(e.getMessage());
			}
		}
		else if(!Connexion_BD.verifMdpSalon(nomSalon, password))
		{
			//Si le mot de passe entré est incorrect
			try {
				this.sortie.writeInt(CodesTchat.codeMotDePasseIncorrect);
			} catch (IOException e) {
				System.out.println(e.getMessage());
			}
		}
		else
		{
			//Salon disponible, client non banni et mot de passe correct
			this.serveur.clientRejointSalon(nomClient, nomSalon);
			Vector<String> clientsSalon = this.serveur.getClientsSalon(nomSalon);
			clientsSalon.remove(nomClient);
			try {
				this.sortie.writeInt(CodesTchat.codeSalonDisponible);
			} catch (IOException e) {
				System.out.println(e.getMessage());
			}
			try {
				this.sortie.writeInt(clientsSalon.size());
			} catch (IOException e) {
				System.out.println(e.getMessage());
			}
			for(int i = 0 ; i < clientsSalon.size(); i++)
			{
				try {
					this.sortie.writeUTF(clientsSalon.elementAt(i));
				} catch (IOException e) {
					System.out.println(e.getMessage());
				}
			}
			
			//TODO Informer les autres clients du salon de l'arrivée d'un nouveau tchatteur
		}
	}

	private void gererCodeSupprimerSalon() {
		String salon = null;
		try {
			salon = this.entree.readUTF();
		} catch (IOException e) {
			System.out.println(e.getMessage());
		}
		
		String mdp = null;
		try {
			mdp = this.entree.readUTF();
		} catch (IOException e) {
			System.out.println(e.getMessage());
		}
		
		try {
			if (! Connexion_BD.estPublic(salon) && ! mdp.equals(Connexion_BD.getMdpSalon(salon)))
			{
				this.sortie.writeInt(CodesTchat.codeErreurSuppressionSalon);
			}
			
			else
			{
				this.sortie.writeInt(CodesTchat.codeSalonSupprime);
				Connexion_BD.SupprimerAdminsSalon(salon);
				Connexion_BD.SupprimerBannisSalon(salon);
				Connexion_BD.SupprimerSalon(salon);
			}
			
		}  catch (Exception e)
		{
			System.out.println(e.getMessage());
		}
			
	}

	private void gererCodeCreationCompte()
		{
			String pseudo = null;
			try {
				pseudo = this.entree.readUTF();
			} catch (IOException e) {
				System.out.println(e.getMessage());
			}
			String mdp = null;
			try {
				mdp = this.entree.readUTF();
			} catch (IOException e) {
				System.out.println(e.getMessage());
			}
			String email = null;
			try {
				email = this.entree.readUTF();
			} catch (IOException e) {
				System.out.println(e.getMessage());
			}
					
			try {
				if (Connexion_BD.verifExistenceCompte(pseudo)) {
					this.sortie.writeInt(CodesTchat.codeErreurCreationCompte);
				}
				
				else
				{
					this.sortie.writeInt(CodesTchat.codeCompteCree);
					Connexion_BD.EnregistrerCompte(pseudo, email, mdp);				
				}
			} catch (Exception e)
			{
				System.out.println(e.getMessage());
			}
		}

	private void gererCodeDeconnexion() {
		//TODO Informer les clients connectés aux salons contenant le tchatteur qui vient de se déconnecter
		try {
			serveur.supprimerClient(nomClient);
			socket.close();
		} catch (IOException e) {
			System.out.println("CodeDeconnexion : Erreur fermeture connexion");
			System.out.println(e.getMessage());
			System.out.println(e.getMessage());
		}
	}

	public void gererCodeConnexion()
	{
		String pseudo = null;
		try {
			pseudo = this.entree.readUTF();
		} catch (IOException e) {
			System.out.println(e.getMessage());
		}
		String password = null;
		try {
			password = this.entree.readUTF();
		} catch (IOException e) {
			System.out.println(e.getMessage());
		}
		
		try {
			if (Connexion_BD.verifMdp(pseudo, password) && this.serveur.MAJClient(nomClient, pseudo))
			{
				this.nomClient = pseudo;
				this.sortie.writeInt(CodesTchat.codeConnexionOK);
				this.sortie.writeInt(0); //nbSalons
			}
			else
			{
				this.sortie.writeInt(CodesTchat.codeConnexionImpossible);
				this.serveur.supprimerClient(nomClient);
			}
		} catch (Exception e)
		{
			System.out.println(e.getMessage());
		}
	}
	
	public void gererCodeCreationSalon()
	{
		String nomSalon = null;
		try {
			nomSalon = this.entree.readUTF();
		} catch (IOException e) {
			System.out.println(e.getMessage());
		}
		String mdp = null;
		try {
			mdp = this.entree.readUTF();
		} catch (IOException e) {
			System.out.println(e.getMessage());
		}
		String description = null;
		try {
			description = this.entree.readUTF();
		} catch (IOException e) {
			System.out.println(e.getMessage());
		}
		int nbPlaces = 0;
		try {
			nbPlaces = this.entree.readInt();
		} catch (IOException e) {
			System.out.println(e.getMessage());
		}
				
		try {
			if (Connexion_BD.verifExistenceSalon(nomSalon)) {
				this.sortie.writeInt(CodesTchat.codeErreurCreationSalon);
			}
			
			else
			{
				this.sortie.writeInt(CodesTchat.codeSalonCree);
				Connexion_BD.EnregistrerSalon(nomSalon, mdp, description, nbPlaces);
			}
		} catch (Exception e)
		{
			System.out.println(e.getMessage());
		}
	}

	public void recupererFlot(Socket transfert)
	{
		try {
			this.in = transfert.getInputStream();
			this.out = transfert.getOutputStream();
			this.entree = new DataInputStream(in);
			this.sortie = new DataOutputStream(out);
		}
		catch (Exception e)
		{
			System.out.println("Erreur de création des flots de données");
		}
	}
}

class Serveur
{
	private int port;
	private ServerSocket socketEcoute;
	private Socket socketTransfert;
	
	private ConcurrentHashMap<String, TraitementClient> clients;
	private ConcurrentHashMap<String, Vector<String> > clientsParSalon;
	private ConcurrentHashMap<String, Vector<String> > salonsParClient;
	
	public static void main(String[] args)
	{
		try {
			Serveur serveur = new Serveur();
		}
		catch (Exception e) {
			System.out.println("Erreur lors de la création du serveur.");
		}
	}
	
	public void clientRejointSalon(String nomClient, String nomSalon) {
		this.clientsParSalon.get(nomSalon).add(nomClient);
		this.salonsParClient.get(nomClient).add(nomSalon);
	}

	public Vector<String> getClientsSalon(String nomSalon) {
		return this.clientsParSalon.get(nomSalon);
	}

	public int nbClients(String nomSalon) {
		return clientsParSalon.get(nomSalon).size();
	}

	public Serveur()
	{
		this.port = 2000;
		this.clients = new ConcurrentHashMap<String, TraitementClient>();
		this.clientsParSalon = new ConcurrentHashMap<String, Vector<String> >();
		this.salonsParClient = new ConcurrentHashMap<String, Vector<String> >();

		creerSocketEcoute();

		while(true)
		{
			attendreConnexion();
			afficherClient();

			TraitementClient t = new TraitementClient(this, Integer.toString(clients.size()), this.socketTransfert);
			clients.put(Integer.toString(clients.size()), t);
			afficherClients();
			t.start();
		}
	}

	public void supprimerClient(String nomClient) {
		this.clients.remove(nomClient);
		
		//Supprime le client dans tous les salons qu'il avait ouverts
		for(String mapKey : clientsParSalon.keySet())
		{
			if(clientsParSalon.get(mapKey).remove(nomClient))
			{
				//Si le client était dans le salon de nom mapKey
				//informer les autres clients du salon
			}
		}
		
		//Supprime le client dans salonsParClient
		this.salonsParClient.remove(nomClient);
	}

	public boolean MAJClient(String nomClient, String pseudo) {
		boolean connexionPossible = !this.clients.containsKey(pseudo); 
		if(connexionPossible)
		{
			this.clients.put(pseudo, this.clients.get(nomClient));
			this.salonsParClient.put(pseudo, new Vector<String>());
		}
		this.clients.remove(nomClient);
		return connexionPossible;
	}

	public void afficherClient()
	{
		System.out.println("Client connecté : IP = " + this.socketTransfert.getInetAddress().getHostAddress() + ":" + this.socketTransfert.getPort());
	}

	public void creerSocketEcoute()
	{
		try {
			this.socketEcoute = new ServerSocket(this.port);
		}
		catch (Exception e)
		{
			System.out.println("Erreur création socket d'écoute");
		}
	}

	public void attendreConnexion()
	{
		try {
			this.socketTransfert = this.socketEcoute.accept();
		}
		catch(Exception e)
		{System.out.println("Erreur attente connexion");}
	}

	public void fermerConnexion()
	{
		try {
			socketTransfert.close();
		}
		catch(Exception e)
		{System.out.println("Erreur fermeture connexion");}
	}
	
	public void afficherClients()
	{
		for(String mapKey : clients.keySet())
		{
			System.out.println(mapKey);
		}
	}
}

